package com.cyf.common.core.io;

import cn.hutool.core.util.IdUtil;
import com.cyf.common.core.coll.CollUtil;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.LocalDate;
import java.util.*;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author chenyifan
 * @create 2024-04-28 18:51
 */
public class FileUtil {

    private static final Pattern FILE_NAME_INVALID_PATTERN_WIN = Pattern.compile("[\\\\/:*?\"<>|]");

    /**
     * 获取文件名称，如果是目录，则返回目录名称
     * @param filePath 文件路径
     * @return
     */
    public static String getFileName(String filePath) {
        return FilenameUtils.getName(filePath);
    }

    /**
     * 根据路径获取文件类型
     * sss.txt      -> txt
     * d:/te/c.txt  -> txt
     * @param fileName
     * @return
     */
    public static String getFileType(String fileName) {
        return FilenameUtils.getExtension(fileName);
    }

    /**
     * 获取该目录及其子目录下的所有文件
     * @param dir 目录
     */
    public static List<File> listFile(File dir) {
        try (Stream<Path> paths = Files.walk(dir.toPath())) {
            // isRegularFile：是否为常规文件
            return paths.filter(Files::isRegularFile).map(Path::toFile).collect(Collectors.toList());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 判断文件是否在指定目录下
     * @param dir 目录路径
     * @param filePath 文件路径
     * @return
     */
    public static boolean isChildOfDirectory(String dir, String filePath) {
        return Paths.get(filePath).startsWith(Paths.get(dir));
    }


    // --------------------------------- 文件读取 -----------------------------------------------

    /**
     * 读取文件内容到字符串
     * @param file 文件
     * @return
     */
    public static String readString(File file) {
        try (FileInputStream is = new FileInputStream(file)) {
            return StreamUtils.copyToString(is, StandardCharsets.UTF_8);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 读取文件每行内容
     * @param file 文件
     * @return
     */
    public static List<String> readAllLines(File file) {
        try {
            return Files.readAllLines(file.toPath());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 读取文件内容到字符串
     * @param file 文件
     * @return
     */
    public static byte[] readBytes(File file) {
        try {
            return Files.readAllBytes(file.toPath());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }




    // ----------------------------------------- 文件写入 -----------------------------------------------------


    /**
     * 将字符串列表写入文件
     * @param file 文件
     * @param lines 写入的内容列表
     */
    public static void writeLinesToFile(File file, List<String> lines) {
        createParentDir(file);
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
            for (String line : lines) {
                writer.write(line);
                writer.newLine();
            }
            writer.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 将字节数组写入到指定文件
     * @param bytes 字节数组
     * @param file 文件
     * @return
     */
    public static void writeBytesToFile(File file, byte[] bytes) throws IOException {
        createParentDir(file);
        if (ArrayUtils.isEmpty(bytes)) {
            return;
        }
        try (FileOutputStream os = new FileOutputStream(file)) {
            os.write(bytes);
        }
    }

    /**
     * 文件追加写
     * @param file 文件
     * @param content 要写入的内容
     */
    public static void appendToFile(File file, String content) throws IOException {
        // 第二个参数为 true 表示追加模式
        try (FileWriter writer = new FileWriter(file, true)) {
            writer.write(content);
            writer.flush();
        }
    }

    /**
     * 文件追加写
     * @param file 文件
     * @param bytes 要写入的字节数组
     */
    public static void appendToFile(File file, byte[] bytes) throws IOException {
        Files.write(file.toPath(), bytes, StandardOpenOption.APPEND);
    }



    // ----------------------------------------- 创建文件 ---------------------------------------------------

    public static void createParentDir(File file) {
        try {
            Files.createDirectories(file.toPath().getParent());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    // ---------------------------------------------- 删除文件和目录 ------------------------------------------------------

    /**
     * 删除文件，如果传入文件夹，文件夹下面有文件会报错
     * @param files 文件，可传多个
     * @throws IOException
     */
    public static void delFile(File... files) {
        try {
            for (File file : files) {
                Files.deleteIfExists(file.toPath());
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 删除目录
     * @param dir 目录
     */
    public static void delDir(File dir){
        // 递归遍历文件和文件夹
        try {
            Files.walkFileTree(dir.toPath(), new SimpleFileVisitor<Path>() {
                // 在访问文件时触发该方法
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    Files.delete(file);
                    return FileVisitResult.CONTINUE;
                }

                // 在访问目录之后触发该方法，此时该目录下的文件已访问完毕
                @Override
                public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
                    Files.delete(dir);
                    return FileVisitResult.CONTINUE;
                }
            });
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }



    // ------------------------------------------ 拷贝文件和目录 ------------------------------------------------

    /**
     * 拷贝文件到指定位置
     * @param src  源文件
     * @param dest 目标文件
     */
    public static void copyFile(File src, File dest) {
        try {
            createParentDir(dest);
            // 如果 dest 存在，替换已存在的文件
            Files.copy(src.toPath(), dest.toPath(), StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 拷贝 src 目录下的文件到 dest
     * @param srcDir 源目录
     * @param destDir 目标目录
     */
    public static void copyDir(File srcDir, File destDir){
        Path src = srcDir.toPath();
        Path dest = destDir.toPath();
        // 创建目标目录，如果不存在
        if (!Files.exists(dest)) {
            try {
                Files.createDirectories(dest);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        // 遍历源目录下的所有条目（文件和目录）
        try (DirectoryStream<Path> paths = Files.newDirectoryStream(src)) {
            for (Path path : paths) {
                // relativize：获取源目录下的文件或子目录相对于源目录的路径
                // resolve：合并到目标目录
                Path targetPath = dest.resolve(src.relativize(path));
                if (Files.isDirectory(targetPath)) {
                    // 如果是目录，则递归调用copyDir方法
                    copyDir(path.toFile(), path.toFile());
                } else {
                    // 如果是文件，则直接复制，会替换已存在的文件
                    Files.copy(path, targetPath, StandardCopyOption.REPLACE_EXISTING);
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


    // ----------------------------------------------- 大文件分割 -----------------------------------------------

    /**
     * 将大文件内容均分为指定数量的小文件
     * @param file
     * @param count
     * @return
     */
    public static List<File> splitFile(File file, int count) {
        List<String> lines = readAllLines(file);
        if (CollectionUtils.isEmpty(lines)) {
            return Collections.emptyList();
        }

        // 内容分割
        List<List<String>> splitList = CollUtil.splitList(lines, count);

        File dir = file.getParentFile();
        String extension = FilenameUtils.getExtension(file.getName());
        // 设置导出的目录
        String exportDir = Paths.get(dir.getPath(), String.format("%s-split-%d", file.getName(), count)).toString();

        List<File> files = new ArrayList<>();
        for (int i = 0; i < splitList.size(); i++) {
            File splitFile = Paths.get(exportDir, String.format("%03d", i + 1) + "." + extension).toFile();
            writeLinesToFile(splitFile, splitList.get(i));
            files.add(file);
        }
        return files;
    }

    /**
     * 获取目录下指定类型的文件并按修改时间逆序排序
     * @param filePath 目录路径
     * @param extension 文件类型
     * @return
     */
    public static List<File> listFilesByTypeSortedByLastModified(String filePath, String extension) {
        return listFilesByTypeSortedByLastModified(filePath, extension, ele -> true);
    }

    /**
     * 获取目录下指定类型的文件并按修改时间逆序排序
     * @param filePath 目录路径
     * @param extension 文件类型
     * @return
     */
    public static List<File> listFilesByTypeSortedByLastModified(String filePath, String extension, Predicate<File> predicate) {
        File dir = Paths.get(filePath).toFile();
        File[] files = dir.listFiles();
        if (files == null) {
            return new ArrayList<>();
        }
        return Arrays.stream(files)
                .filter(ele -> ele.isFile() && extension.equalsIgnoreCase(FilenameUtils.getExtension(ele.getName())) && predicate.test(ele))
                .sorted(Comparator.comparing(File::lastModified).reversed())
                .collect(Collectors.toList());
    }

    /**
     * 文件名中是否包含在Windows下不支持的非法字符，包括： \ / : * ? " &lt; &gt; |
     *
     * @param fileName 文件名（必须不包括路径，否则路径符将被替换）
     * @return 是否包含非法字符
     * @since 3.3.1
     */
    public static boolean containsInvalid(String fileName) {
        return !StringUtils.hasText(fileName) || FILE_NAME_INVALID_PATTERN_WIN.matcher(fileName).find();
    }
}